package com.ling.module1sbbyyds.server;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ling.sbbyyds.entity.news.Message;
import com.ling.sbbyyds.entity.news.MessageLastReadTime;
import com.ling.sbbyyds.entity.users.User;
import com.ling.module1sbbyyds.service.news.MessageService;
import com.ling.sbbyyds.mapper.news.MessageLastReadMapper;
import com.ling.sbbyyds.mapper.news.MessageMapper;
import com.ling.sbbyyds.mapper.user.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * websocket  实现聊天功能
 * 1.用户进行连接
 * 1.1.判断用户是否登录（校验token）
 * 1.2.检验通过，进行连接，把用户session信息进行保存   session <--> token
 * 2.用户发送消息
 * 2.1.
 * 2.2.
 *
 * @author zhengkai.blog.csdn.net
 */
@Slf4j
//configurator = MyEndpointConfig.class  注入bean关键
//@ServerEndpoint(value = "/imserver/{token}", configurator = MyEndpointConfig.class)
@Component
public class WebSocketServer {

    @Autowired(required = false)
    private MessageMapper messageMapper;

    @Autowired(required = false)
    private UserMapper userMapper;

    @Autowired
    private MessageService messageService;

    @Autowired
    private MessageLastReadMapper messageLastReadMapper;

    @Autowired
    private RedisTemplate<String, String> redis;

    /**
     * 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
     */
//    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();

    /**
     * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<Long, Session> webSocketMapUserList = new ConcurrentHashMap<>();

    /**
     * 校验token
     *
     * @param token
     * @return User
     */
    public User checkToken(String token) {
        User user = null;
        if (token != null) {
            //获取redis中是否有这个token   key:token  value: User(用户信息)
            String jsonUser = redis.opsForValue().get(token);
            if (StringUtils.isNotBlank(jsonUser)) {
                user = JSONObject.parseObject(jsonUser, User.class);
            }
            return user;
        }
        return null;
    }

    /**
     * 插入或更新用户最后一次获取聊天记录的时间
     *
     * @param userId 用户id
     * @return
     */
    public boolean insertUserLastReadTime(Long userId) {
        MessageLastReadTime messageLastReadTime = messageLastReadMapper.selectByPrimaryKey(userId);
        int i;
        if (ObjectUtils.isEmpty(messageLastReadTime)) {
            //不存在记录，插入记录
            messageLastReadTime = new MessageLastReadTime();
            messageLastReadTime.setToUserId(userId);
            messageLastReadTime.setLastReadTime(new Date());
            i = messageLastReadMapper.insertSelective(messageLastReadTime);
        } else {
            //存在记录，修改记录
            messageLastReadTime.setLastReadTime(new Date());
            i = messageLastReadMapper.updateByPrimaryKeySelective(messageLastReadTime);
        }
        return i > 0;
    }

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        //该用户未连接
        User user = checkToken(token);
        //1.1.判断用户是否登录（校验token）
        if (ObjectUtils.isEmpty(user)) {
            log.error("用户未登录！");
            return;
        }
        //判断是否存在该用户
        if (webSocketMapUserList.containsKey(user.getId())) {
            log.warn(user.getId() + "用户已经建立连接！");
            //该用户已连接，不用进行连接
            return;
            //加入set中
        } else {
            //1.2.检验通过，进行连接，把用户session信息进行保存   userId <--> session
            webSocketMapUserList.put(user.getId(), session);
            //加入set中
            addOnlineCount();
            //在线数加1
        }
        log.info("用户连接:" + user.getId() + ",当前在线人数为:" + getOnlineCount());
        try {
            sendMessage("连接成功", session);

            //@TODO 把用户离线未收到的消息发送给用户
            List<List<Message>> allUserUnreadNews = messageService.getAllUserUnreadNews(user.getId());
            //有未接受的信息，重新上线后进行消息提醒
            if (!ObjectUtils.isEmpty(allUserUnreadNews)) {
                JSONObject jsonObject = new JSONObject();
                //设置离线消息提醒，前端特殊处理
                jsonObject.put("messageType", "off-line");
                jsonObject.put("messageInfo", JSON.toJSONString(allUserUnreadNews));
                log.info("有离线消息：{}", jsonObject);
                sendMessage(jsonObject.toJSONString(), session);
                //更改最后一次获取离线消息的时间
                boolean b = insertUserLastReadTime(user.getId());
                log.info("更改最后一次获取离线消息的时间结果：" + b);
            } else {
                log.warn("没有离线消息");
            }
        } catch (IOException e) {
            log.error("用户:" + user.getId() + ",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        Long userId = null;
        for (Map.Entry<Long, Session> entry : webSocketMapUserList.entrySet()) {
            if (session.equals(entry.getValue())) {
                webSocketMapUserList.remove(entry.getKey());
                //从set中删除
                subOnlineCount();
                userId = entry.getKey();
                break;
            }
        }
        log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     *                格式：{"token":"eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTU5MjEzMTYsInVzZXJJZCI6NywiaWF0IjoxNjU1MDMyMjgzfQ.7yKn8a9-z_tQH6LRroGj2vqBbNf-bAp5achlx6IdQhY", "sendUserId":"1","toUserId":"2","message":"你好，用户2！" }
     */
    @OnMessage
    public void onMessage(String message) {
        //可以群发消息
        //消息保存到数据库、redis
        if (StringUtils.isNotBlank(message)) {
            try {
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                //获取要发送给的用户
                Long sendUserId = jsonObject.getLong("sendUserId");
                //toUserId为空，发送失败
                if (ObjectUtils.isEmpty(sendUserId)) {
                    log.error("请求的sendUserId字段为空！");
                    return;
                }
                log.info("用户消息:" + sendUserId + ",发送报文:" + message);
                //获取要发送给的用户
                Long toUserId = jsonObject.getLong("toUserId");
                //toUserId为空，发送失败
                if (ObjectUtils.isEmpty(toUserId)) {
                    log.error("请求的toUserId字段为空！");
                    return;
                }

                //@TODO 判断用户是否关注了要发送给的用户（是就进行发送）
                boolean friendFlag = false;
                if (friendFlag) {

                }

                //获取发送信息内容
                String messageInfo = jsonObject.getString("message");
                //messageInfo为空，发送失败
                if (StringUtils.isBlank(messageInfo)) {
                    log.error("请求的message字段为空！");
                    return;
                }
                //追加发送人,发送该条信息的用户(防止串改)
                jsonObject.put("sendUserId", sendUserId);
                //传送给对应toUserId用户的websocket，用户在线
                if (webSocketMapUserList.containsKey(toUserId)) {
                    //发送信息给用户（客户端）
                    //移除token，避免一起发给用户
                    jsonObject.remove("token");
                    //加入用户头像和用户名和发布时间
                    User user = userMapper.selectByPrimaryKey(sendUserId);
                    jsonObject.put("userName", user.getUserName());
                    jsonObject.put("headImage", user.getAvatar());
                    jsonObject.put("releaseTime", new Date());
                    sendMessage(jsonObject.toJSONString(), webSocketMapUserList.get(toUserId));
                } else {
                    log.warn("请求的userId:" + toUserId + "不在该服务器上，即用户不在线！");
                    //否则不在这个服务器上，发送到mysql或者redis
                    Message messageNew = new Message();
                    messageNew.setMessage(messageInfo);
                    messageNew.setToUserId(Long.valueOf(toUserId));
                    messageNew.setSendUserId(sendUserId);
                    //发送时间
                    messageNew.setSendTime(new Date());
                    int i = messageMapper.insertSelective(messageNew);
                    if (i > 0) {
                        log.info("发送信息成功:", messageNew.getMessage());
                    } else {
                        log.error("发送信息成功失败:", messageNew.getMessage());
                    }
                }
            } catch (JSONException e) {
                log.error("信息不是Json格式:", message);
            } catch (Exception e) {
                log.error("发送信息发生未知错误", e.getMessage());
                e.printStackTrace();
            }
        }
    }

    /**
     * 连接出现错误
     *
     * @param error
     */
    @OnError
    public void onError(Throwable error) {
        error.printStackTrace();
    }


    /**
     * 实现服务器主动推送消息 给客户端
     */
    public static void sendMessage(String message, Session session) throws IOException {
        session.getBasicRemote().sendText(message);
    }


    /**
     * 发送自定义消息给指定用户
     */
    public static void sendInfo(String message, @PathParam("userId") String userId, Session session) throws IOException {
        log.info("发送消息到:" + userId + "，报文:" + message);
        if (StringUtils.isNotBlank(userId) && webSocketMapUserList.containsKey(userId)) {
            sendMessage(message, session);
        } else {
            log.error("用户" + userId + ",不在线！");
        }
    }


    /**
     * 获取在线人数
     *
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 在线人数加1
     */
    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }


    /**
     * 在线人数减1
     */
    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

}
